using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Lockstep.Collision2D;
using Lockstep.Logging;
using Lockstep.Math;
using Lockstep.Game;
using Lockstep.Serialization;
using Lockstep.Util;
using System.Linq;
using System.Reflection;
using Sirenix.OdinInspector.Demos.RPGEditor;

namespace Lockstep.Game
{

    public enum EntityType
    {
        None,
        Player,
        Enemy,
        MapItem,
        Bullet
    }
    public partial class GameStateService : BaseGameService, IGameStateService
    {
        private Dictionary<int, GameState> _tick2State = new Dictionary<int, GameState>();
        private GameState _curGameState;
        private Dictionary<EntityType, List<BaseEntity>> _type2Entities = new Dictionary<EntityType, List<BaseEntity>>();
        private Dictionary<int, BaseEntity> _id2Entities = new Dictionary<int, BaseEntity>();
        private Dictionary<int, Serializer> _tick2Backup = new Dictionary<int, Serializer>();
        private List<BaseEntity> deadList = new List<BaseEntity>();

        Array typeList = Enum.GetValues(typeof(EntityType));
        public override void DoAwake(IServiceContainer services)
        {
            base.DoAwake(services);
        }

        public void DoUpdate(LFloat deltaTime)
        {
            deadList.Clear();
            foreach (var t in typeList)
            {
                if (_type2Entities.TryGetValue((EntityType)t, out List<BaseEntity> le))
                {
                    foreach (var e in le)
                    {
                        e.DoUpdate(deltaTime);
                        if (e.isDead)
                        {
                            deadList.Add(e);
                        }

                    }
                }
            }
            foreach (var dead in deadList)
            {
                PhysicSystem.Instance?.RemoveCollider(dead);
                DestroyEntity(dead);
            }
        }

        public override void DoDestroy()
        {
            base.DoDestroy();
            foreach (var e in _id2Entities.Values)
            {
                e.DoDestroy();
            }
            _id2Entities.Clear();
            _type2Entities.Clear();
        }
        private void AddEntity(EntityType type, BaseEntity e)
        {
            Debug.Log("Add AddEntity");
            if (_type2Entities.TryGetValue(type, out var lst))
            {
                lst.Add(e);
            }
            else
            {
                lst = new List<BaseEntity>();
                _type2Entities.Add(type, lst);
                lst.Add(e);
            }

            _id2Entities[e.EntityId] = e;
        }

        private void RemoveEntity<T>(T e) where T : BaseEntity
        {
            if (_type2Entities.TryGetValue(e.entityType, out var lstObj))
            {
                lstObj.Remove(e);
                Debug.Log("Remove Entity");
                _id2Entities.Remove(e.EntityId);
            }
            else
            {
                Debug.LogError("Try remove a deleted Entity" + e);
            }
        }

        public List<BaseEntity> GetEntities(EntityType t)
        {
            if (_type2Entities.TryGetValue(t, out var lst))
            {
                return lst;
            }
            else
            {
                lst = new List<BaseEntity>();
                _type2Entities.Add(t, lst);
                return lst;
            }
        }


        public object GetEntity(int id)
        {
            if (_id2Entities.TryGetValue(id, out var val))
            {
                return val;
            }

            return null;
        }

        public T CreateEntity<T>(int id, LVector3 position) where T : Entity, new()
        {
            var baseEntity = new T();
            baseEntity.EntityId = _idService.GenId();
            baseEntity.entityType = (baseEntity is Player) ? EntityType.Player : EntityType.Enemy;
            baseEntity.colliderLayer = (baseEntity is Player) ? EColliderLayer.Player : EColliderLayer.Enemy;
            baseEntity.GameStateService = _gameStateService;
            baseEntity.ServiceContainer = _serviceContainer;
            baseEntity.DebugService = _debugService;
            baseEntity.transform.Pos3 = position;
            baseEntity.InitData(_gameConfigService.GetEntityConfig(id));
            _debugService.Trace($"CreateEntity {id} pos {position} entityId:{baseEntity.EntityId}");
            baseEntity.DoBindRef();
            if (baseEntity is Entity entity)
            {
                PhysicSystem.Instance.RegisterEntity(10000 + id, entity, baseEntity.colliderLayer);
            }

            baseEntity.DoAwake();
            baseEntity.DoStart();
            _gameViewService.BindView(baseEntity);
            AddEntity((baseEntity is Player) ? EntityType.Player : EntityType.Enemy, baseEntity);
            return baseEntity;
        }

        public void DestroyEntity(BaseEntity entity)
        {
            RemoveEntity(entity);
        }
        public T CreateMapItem<T>(WallCellConfig info, LVector3 position) where T : MapItemEntity, new()
        {
            T baseEntity = new T();
            baseEntity.EntityId = _idService.GenId();
            baseEntity.entityType = EntityType.MapItem;
            baseEntity.config = info;
            baseEntity.colliderLayer = EColliderLayer.Static;
            baseEntity.InitData(info);
            baseEntity.GameStateService = _gameStateService;
            baseEntity.ServiceContainer = _serviceContainer;
            baseEntity.DebugService = _debugService;
            baseEntity.transform.Pos3 = position;
            _debugService.Trace($"CreateMapItem {info.type} pos {position} entityId:{baseEntity.EntityId}");
            baseEntity.DoBindRef();

            if (info.isCollider)
            {
                PhysicSystem.Instance.RegisterEntity(20000 + (int)info.type, baseEntity, baseEntity.colliderLayer);
            }

            baseEntity.DoAwake();
            baseEntity.DoStart();
            _gameViewService.BindView(baseEntity);
            AddEntity(EntityType.MapItem, baseEntity);
            return baseEntity;
        }

        public T CreateBullet<T>(BulletConfig config, Entity caster, LVector3 position) where T : BulletEntity, new()
        {
            T baseEntity = new T();
            baseEntity.EntityId = _idService.GenId();
            baseEntity.entityType = EntityType.Bullet;
            baseEntity.config = config;
            baseEntity.caster = caster;
            baseEntity.colliderData = config.colliderData;
            baseEntity.colliderLayer = EColliderLayer.Bullet;
            baseEntity.GameStateService = _gameStateService;
            baseEntity.ServiceContainer = _serviceContainer;
            baseEntity.DebugService = _debugService;
            baseEntity.transform.Pos3 = position;
            _debugService.Trace($"CreateBulletItem {config.type} pos {position} entityId:{baseEntity.EntityId}");
            baseEntity.DoBindRef();

            if (config.isCollider)
            {
                PhysicSystem.Instance.RegisterEntity(30000 + (int)config.type, baseEntity, baseEntity.colliderLayer);
            }

            baseEntity.DoAwake();
            baseEntity.DoStart();
            _gameViewService.BindView(baseEntity);
            AddEntity(EntityType.Bullet, baseEntity);
            return baseEntity;
        }

        public override void Backup(int tick)
        {
            base.Backup(tick);
            _tick2State[tick] = _curGameState;
            Serializer writer = new Serializer();
            writer.Write(_commonStateService.Hash); //hash

            BackUpEntities(GetEntities(EntityType.Player), writer);
            BackUpEntities(GetEntities(EntityType.Enemy), writer);
            BackUpEntities(GetEntities(EntityType.MapItem), writer);
            BackUpEntities(GetEntities(EntityType.Bullet), writer);
            _tick2Backup[tick] = writer;
        }
        private Type[] canBackupTypes = new Type[] {
            typeof(Player), typeof(Enemy), typeof(PlayerSpawner), typeof(EnemySpawner), typeof(HomeMapItem) ,
            typeof(BrickMapItem), typeof(BarrierMapItem), typeof(WaterMapItem), typeof(GrassMapItem)
        };
        public override void RollbackTo(int tick)
        {
            base.RollbackTo(tick);
            _curGameState = _tick2State[tick];
            if (_tick2Backup.TryGetValue(tick, out var backupData))
            {
                //.TODO reduce the unnecessary create and destroy 
                var reader = new Deserializer(backupData.Data);
                var hash = reader.ReadInt32();
                _commonStateService.Hash = hash;

                var oldId2Entity = _id2Entities;
                _id2Entities = new Dictionary<int, BaseEntity>();
                _type2Entities.Clear();

                //. Recover Entities
                RecoverEntities(new List<BaseEntity>(), EntityType.Player, reader);
                RecoverEntities(new List<BaseEntity>(), EntityType.Enemy, reader);
                RecoverEntities(new List<BaseEntity>(), EntityType.MapItem, reader);

                RecoverEntities(new List<BaseEntity>(), EntityType.Bullet, reader);
                //. Rebind Ref
                foreach (var entity in _id2Entities.Values)
                {
                    entity.GameStateService = _gameStateService;
                    entity.ServiceContainer = _serviceContainer;
                    entity.DebugService = _debugService;
                    entity.DoBindRef();
                }

                //. Rebind Views 
                foreach (var pair in _id2Entities)
                {
                    BaseEntity oldEntity = null;
                    if (oldId2Entity.TryGetValue(pair.Key, out var poldEntity))
                    {
                        oldEntity = poldEntity;
                        oldId2Entity.Remove(pair.Key);
                    }
                    _gameViewService.BindView(pair.Value, oldEntity);
                }

                //. Unbind Entity views
                foreach (var pair in oldId2Entity)
                {
                    _gameViewService.UnbindView(pair.Value);
                }
            }
            else
            {
                Debug.LogError($"Miss backup data  cannot rollback! {tick}");
            }
        }


        public override void Clean(int maxVerifiedTick)
        {
            base.Clean(maxVerifiedTick);
        }

        void BackUpEntities<T>(List<T> lst, Serializer writer) where T : BaseEntity, new()
        {
            writer.Write(lst.Count);
            foreach (var item in lst)
            {
                if (item is IBackup backup)
                    backup.WriteBackup(writer);
            }
        }

        List<BaseEntity> RecoverEntities(List<BaseEntity> lst, EntityType type, Deserializer reader)
        {
            var count = reader.ReadInt32();
            for (int i = 0; i < count; i++)
            {
                var t = new BaseEntity();
                lst.Add(t);
                if (t is IBackup backup)
                    backup.ReadBackup(reader);
            }

            _type2Entities[type] = lst;
            foreach (var e in lst)
            {
                _id2Entities[e.EntityId] = e;
            }

            return lst;
        }

        public struct GameState
        {
            public LFloat RemainTime;
            public LFloat DeltaTime;
            public int MaxEnemyCount;
            public int CurEnemyCount;
            public int CurEnemyId;

            public int GetHash(ref int idx)
            {
                int hash = 1;
                hash += CurEnemyCount.GetHash(ref idx) * PrimerLUT.GetPrimer(idx++);
                hash += MaxEnemyCount.GetHash(ref idx) * PrimerLUT.GetPrimer(idx++);
                hash += CurEnemyId.GetHash(ref idx) * PrimerLUT.GetPrimer(idx++);
                return hash;
            }
        }

        public LFloat RemainTime
        {
            get => _curGameState.RemainTime;
            set => _curGameState.RemainTime = value;
        }

        public LFloat DeltaTime
        {
            get => _curGameState.DeltaTime;
            set => _curGameState.DeltaTime = value;
        }

        public int MaxEnemyCount
        {
            get => _curGameState.MaxEnemyCount;
            set => _curGameState.MaxEnemyCount = value;
        }

        public int CurEnemyCount
        {
            get => _curGameState.CurEnemyCount;
            set => _curGameState.CurEnemyCount = value;
        }

        public int CurEnemyId
        {
            get => _curGameState.CurEnemyId;
            set => _curGameState.CurEnemyId = value;
        }
    }
}